﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;
using DigtiMatrix.Entity;
using DigitMatrix.DataAccess;
using log4net;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using OpenCvSharp;

namespace DigitMatrix.Business
{
    public class CameraDriver : IComparable, IDisposable
    {
        private const ushort _OSD_BASE = 0x9000;
        private const ushort _OSD_YEAR4 = _OSD_BASE + 0;
        private const ushort _OSD_YEAR2 = _OSD_BASE + 1;
        private const ushort _OSD_MONTH3 = _OSD_BASE + 2;
        private const ushort _OSD_MONTH2 = _OSD_BASE + 3;
        private const ushort _OSD_DAY = _OSD_BASE + 4;
        private const ushort _OSD_WEEK3 = _OSD_BASE + 5;
        private const ushort _OSD_CWEEK1 = _OSD_BASE + 6;
        private const ushort _OSD_HOUR24 = _OSD_BASE + 7;
        private const ushort _OSD_HOUR12 = _OSD_BASE + 8;
        private const ushort _OSD_MINUTE = _OSD_BASE + 9;
        private const ushort _OSD_SECOND = _OSD_BASE + 10;
        private Thread thread = null;
        private ManualResetEvent stopEvent = null;
        private CamercaInfo cameraInfo = null;
        private IntPtr ChannelHandle;
        private byte[] FileHeader = new byte[200];
        //文件头长度
        private int FileHeaderLen = 0;
        //是否开始捕获文件 0 未启用 1 启用
        private volatile int CaptureState;
        private static IMAGE_STREAM_CALLBACK image_CallBack;
        string videoFile = "";
        uint endCode = 0x00000002;
        public bool IsValidChannel { get; set; }
        private static STREAM_DIRECT_READ_CALLBACK stream_callback;//(int channelNumber, IntPtr DataBuf, int Length, FrameType_t FrameType, IntPtr context);
        public delegate void MotionEvent(SysEvent osysEvent);
        public event MotionEvent TriggMotionEvent;
        Database db;// = DatabaseFactory.CreateDatabase();
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private BinaryWriter _writer;
        private System.IO.FileStream _stream;
        private DateTime _lastRecordTime;
        private bool _videoLostReported;

        private bool _motionDetected;
        private DateTime _lastMotionStartTime;
        private OpenCvSharp.CvRect _lastRect;

        public bool Running
        {
            get
            {
                if (thread != null)
                {
                    if (thread.Join(0) == false)
                        return true;
                    Free();
                }
                return false;
            }
        }

        public CameraDriver(CamercaInfo camera)
        {
            log.Debug("start");
            ChannelHandle = HikVisionSDK.ChannelOpen(camera.ChannelNo);
            if ((uint)ChannelHandle.ToInt32() == 0xFFFFFFFF)
            {
                IsValidChannel = false;
            }
            else
            {
                IsValidChannel = true;
            }
            db = DatabaseFactory.CreateDatabase();

            camera.ChannelHandle = ChannelHandle;
            //设置编码帧结构、帧率
            HikVisionSDK.SetIBPMode(ChannelHandle, 100, 2, 1, 25);
            //设置编码图像质量
            HikVisionSDK.SetDefaultQuant(ChannelHandle, 15, 15, 20);

            HikVisionSDK.SetEncoderPictureFormat(ChannelHandle, PictureFormat_t.ENC_4CIF_FORMAT);

            // ushort[] Format1 = new ushort[] { 48, 16, '摄', '像', '机', '\0' };
            //ushort[] Format1 = new ushort[] { 2, 150, '杭', '州', '海', '康', '威', '视', '\0' };
            // ushort[] Format1 = new ushort[] { 2, 150, 'V', 'I', 'D', 'E', 'O', '威', '\0' };
            ushort[] Format1 = new ushort[3];
            Format1[0] = 48;
            Format1[1] = 16;
            //Format1[2] =(ushort) camera.CamercaId;
            //for (int i = 0; i < camera.Name.Length; i++)
            //{
            //    Format1[i + 2] = camera.Name[i];//(ushort) (((camera.Name[i] & 0XFF) << 8) | (camera.Name[i] & 0XFF00)>>8);
            //}
            Format1[2] = '\0';
            int iRtn;
            ushort[] Format2 = new ushort[] { 24, 33, _OSD_YEAR4, '-', _OSD_MONTH2, '-', _OSD_DAY, '-', _OSD_HOUR24, ':', _OSD_MINUTE, ':', _OSD_SECOND, '\0' };

            iRtn = HikVisionSDK.SetOsdDisplayMode(ChannelHandle, 150, true, 1, Format1, Format2);
            //iRtn = HikVisionSDK.SetOsdDisplayModeEx(ChannelHandle, 150, true, 1,  Format2);
            iRtn = HikVisionSDK.SetOsd(ChannelHandle, true);

            cameraInfo = camera;
            iRtn = HikVisionSDK.SetOverlayColorKey(camera.BackColor.ToArgb());
            StartVideoPreview();



            image_CallBack = new IMAGE_STREAM_CALLBACK(ImageCallBack);
            //捕获图片 该方法备用
            //HikVisionSDK.RegisterImageStreamCallback(image_CallBack, ChannelHandle);
            //捕获视频
            stream_callback = new STREAM_DIRECT_READ_CALLBACK(StreamCallBack);

            HikVisionSDK.RegisterStreamDirectReadCallback(stream_callback, camera.FormHandle);

            motionDetector = new MotionDetectorWrapper.MotionDetector();

        }

        public void Start()
        {
            if (!IsValidChannel)
            {
                return;
            }
            if (thread == null)
            {

                stopEvent = new ManualResetEvent(false);
                thread = new Thread(new ThreadStart(WorkerThread));
                thread.IsBackground = true;
                thread.Name = cameraInfo.Name;
                thread.Start();

                RecordVideo();
            }
        }
        // Signal thread to stop work
        public void SignalToStop()
        {
            // stop thread
            if (thread != null)
            {
                // signal to stop
                stopEvent.Set();
                StopVideo();
            }
        }

        // Wait for thread stop
        public void WaitForStop()
        {
            if (thread != null)
            {
                // wait for thread stop
                thread.Join();

                Free();
            }
        }

        // Abort thread
        public void Stop()
        {
            StopVideo();
            if (this.Running)
            {
                thread.Abort();
                WaitForStop();

            }


        }

        // Free resources
        private void Free()
        {
            thread = null;

            // release events
            stopEvent.Close();
            stopEvent = null;
        }

        public void WorkerThread()
        {
            if (!IsValidChannel)
            {
                return;
            }
            Random rnd = new Random((int)DateTime.Now.Ticks);
            DateTime start;
            bool isStartRecord = false;
            DateTime date1 = DateTime.Now;
            while (true)
            {
                try
                {
                    start = DateTime.Now;
                    while ((stopEvent.WaitOne(0, true) == false))
                    {
                        // motion chenck
                        ProcessMotion();
                        Thread.Sleep(50);
                        //check video
                        if (!IsValidVideo())
                        {
                            if (!_videoLostReported)
                            {
                                SysEvent oSysEvent = new SysEvent();
                                oSysEvent.EventType = (int)Config.EventType.LostVideo;
                                oSysEvent.IsStart = true;
                                oSysEvent.CamercaId = cameraInfo.CamercaId;
                                oSysEvent.EventDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                                oSysEvent.EventDescription = CommonEnumData.GetType<Config.EventType>(Config.EventType.LostVideo.ToString());
                                oSysEvent.StartDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                                oSysEvent.EndDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                                oSysEvent.IsStart = false;
                                oSysEvent.EndDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                                EventTypeDataAccess.Insert(db, oSysEvent);
                                _videoLostReported = true;
                            }
                        }
                        else
                        {
                            _videoLostReported = false;
                        }
                        //check disk space
                        if (DateTime.Now - date1 > TimeSpan.FromMinutes(5))
                        {
                            MonitorFolder monitorFolder = new MonitorFolder(Config.VideoPath, Config.SaveDay);
                            if (MonitorFolder.GetFreeDiskSpaceMB(Config.VideoPath) <= Config.DiskSpace)
                            {
                                try
                                {
                                    GeneratorFileInfo.DeleteOldestDate(this.cameraInfo.CamercaId);
                                }
                                catch (System.Exception ex)
                                {
                                	
                                }
                            }
                        }

                    }

                }
                finally
                {

                }

                // need to stop ?
                if (stopEvent.WaitOne(0, true))
                    break;
            }
        }

        MotionDetectorWrapper.MotionDetector motionDetector = null;
        SysEvent oldSysEvent = null;
        private void ProcessMotion()
        {
            try
            {
                if (!IsValidChannel)
                {
                    return;
                }

                var img = GetJpegImage();
                if (img == null)
                {
                    log.Error("pic error");
                    return;
                }

                var pFrame = IplImage.FromBitmap(img);
                img.Dispose();
                CvRect rect = new CvRect(Config.DetectVideoStartX, Config.DetectVideoStartY, Config.DetectVideoWidth, Config.DetectVideoHeight);
                if (rect.Width == 0 || rect.Height == 0)
                {
                    rect.Width = pFrame.Width;
                    rect.Height = pFrame.Height;
                }
              
                //ROI改变后要重新创建detector，否则会出错。
                if (rect != _lastRect)
                {
                    if (motionDetector != null)
                    {
                        //motionDetector.Dispose();
                    }
                    System.Diagnostics.Debug.WriteLine("recreate motion detector");
                    motionDetector = new MotionDetectorWrapper.MotionDetector();
                }
               
                System.Diagnostics.Debug.WriteLine(string.Format("==={0},{1},{2},{3}====", rect.X, rect.Y, rect.Width, rect.Height));

                int resCount = motionDetector.FindObject(pFrame, rect);
                _lastRect = rect;
                
                pFrame.Dispose();

                if (_motionDetected)//in motion state
                {
                    if (resCount <= 0)//motion end
                    {
                        _motionDetected = false;

                        oldSysEvent.IsStart = false;
                        oldSysEvent.EndDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                        DataSet ds = EventTypeDataAccess.GetEventByCamercaidVideoFile(db, cameraInfo.CamercaId, oldSysEvent.VideoFile);
                        if (ds.Tables[0].Rows.Count == 0)
                        {
                            EventTypeDataAccess.Insert(db, oldSysEvent);
                        }
                        if (TriggMotionEvent != null)
                        {
                            TriggMotionEvent(oldSysEvent);
                        }
                    }
                }
                else//normal state
                {
                    if (resCount >= 1)//motion detected;
                    {

                        if (DateTime.Now - _lastMotionStartTime >= TimeSpan.FromMinutes(1))
                        {
                            _motionDetected = true;

                            oldSysEvent = new SysEvent();
                            oldSysEvent.EventType = (int)Config.EventType.WarnInfo;
                            oldSysEvent.VideoFile = videoFile;
                            oldSysEvent.IsStart = true;
                            oldSysEvent.CamercaId = cameraInfo.CamercaId;
                            oldSysEvent.StartDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                            oldSysEvent.EventDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                            oldSysEvent.EventDescription = CommonEnumData.GetType<Config.EventType>(Config.EventType.WarnInfo.ToString());
                            if (TriggMotionEvent != null)
                            {
                                TriggMotionEvent(oldSysEvent);
                            }

                            _lastMotionStartTime = DateTime.Now;

                        }


                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message + " " + ex.StackTrace);
            }

        }
        private void ImageCallBack(int channelNumber, IntPtr context)
        {

        }
        //STREAM_DIRECT_READ_CALLBACK(int channelNumber, IntPtr DataBuf, int Length, FrameType_t FrameType, IntPtr context);
        private int StreamCallBack(int channelNumber, IntPtr DataBuf, int Length, FrameType_t frameType, IntPtr context)
        {
            bool breakable = false;
            if (frameType > 0)
            {
                if (frameType == FrameType_t.PktSysHeader)
                {
                    Marshal.Copy(DataBuf, FileHeader, 0, Length);
                    FileHeaderLen = Length;
                }

                if (frameType == FrameType_t.PktMotionDetection)
                {
                    return 0;
                }

                breakable = (frameType == FrameType_t.PktIFrames || frameType == FrameType_t.PktSubIFrames);
            }

            DateTime now = DateTime.Now;
            if ((now - _lastRecordTime >= TimeSpan.FromMinutes(3)) && breakable)
            {
                CloseLastFile();
                OpenNewFile();
            }

            if (_writer != null)
            {
                byte[] buf = new byte[Length];
                Marshal.Copy(DataBuf, buf, 0, buf.Length);
                _writer.Write(buf, 0, buf.Length);
            }

            return 0;
        }

        private void OpenNewFile()
        {
            DateTime now = DateTime.Now;
            string path = GeneratorFileInfo.GenerateSaveFilePath(Config.VideoPath, "264", cameraInfo.CamercaId, now);
            videoFile = path;
            _stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read);
            _writer = new BinaryWriter(_stream);
            if (FileHeaderLen > 0)
            {
                _writer.Write(FileHeader, 0, FileHeaderLen);
            }
            _lastRecordTime = now;
        }

        private void CloseLastFile()
        {
            if (_writer != null)
            {
                _writer.Close();
            }

            if (_stream != null)
            {
                _stream.Close();
            }

            videoFile = null;
        }

        public void StartVideoPreview()
        {
            if (!IsValidChannel)
            {
                return;
            }
            Rectangle rect = cameraInfo.Rect;
            HikVisionSDK.SetPreviewOverlayMode(false);
            HikVisionSDK.StartVideoPreview(ChannelHandle, cameraInfo.Handle, ref rect, false, (int)TypeVideoFormat.vdfYUV422Planar, 25);

        }
        public Rectangle Rect
        {
            set
            {
                cameraInfo.Rect = value;
            }
        }
        public void MoveWindow()
        {
            if (!IsValidChannel)
            {
                return;
            }
            HikVisionSDK.StopVideoPreview(ChannelHandle);
            HikVisionSDK.SetPreviewOverlayMode(true);
            //Rectangle rect = cameraInfo.Rect;
            //HikVisionSDK.StartVideoPreview(ChannelHandle, cameraInfo.Handle, ref rect, true, (int)TypeVideoFormat.vdfRGB16, 25);
            StartVideoPreview();
        }

        public int RecordVideo()
        {
            if (!IsValidChannel)
            {
                return -1;
            }
            int iRtn = HikVisionSDK.StartVideoCapture(ChannelHandle);
            if (FileHeaderLen == 0)
            {
                return -1;
            }
          
            CaptureState = 1;
            return iRtn;
        }
        public int StopVideo()
        {
            HikVisionSDK.StopVideoCapture(ChannelHandle);
            return 1;
        }
        /// <summary>
        /// 将数据流写入视频文件
        /// </summary>
        /// <param name="length"></param>
        /// <param name="dataBuf"></param>
        private void WriterVideoCapture(int length, IntPtr dataBuf)
        {
           
        }
       
        public Bitmap GetJpegImage()
        {
            if (!IsValidChannel)
            {
                return null;
            }
            
            byte[] imageBuf = new byte[HikVisionSDK.IMAGE_BUFFER_SIZE];
            int size = HikVisionSDK.IMAGE_BUFFER_SIZE;
            uint nQuality = 90;
            var succeed = HikVisionSDK.GetJpegImage(ChannelHandle, imageBuf, out size, nQuality);
            if (succeed == 0)
            {
                MemoryStream ms = new MemoryStream(imageBuf);
                var image = (Bitmap)Image.FromStream(ms, true);
                return image;
            }
            else
            {
                return null;
            }
        }

        public int Close()
        {
            StopVideo();
            if (thread != null)
            {
                thread.Abort();
            }
            Free();

            return HikVisionSDK.ChannelClose(ChannelHandle);

        }
        public bool IsValidVideo()
        {
            if (!IsValidChannel)
            {
                return false;
            }
            if (HikVisionSDK.GetVideoSignal(ChannelHandle) == 0)
            {
                return true;
            }
            return false;
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            System.Diagnostics.Debug.WriteLine("=====dispose camera driver=======");

            StopVideo();
            CleanManagedResource();
            CleanNativeResource();
            GC.SuppressFinalize(this);
        }

        #endregion

        ~CameraDriver()
        {
            CleanNativeResource();
        }

        private void CleanManagedResource()
        {
            CloseLastFile();
        }

        private void CleanNativeResource()
        {
        }
    }
}
